package util

import (
	"encoding/json"
	"fmt"
	"log"

	sourceAST "github.com/graphql-go/graphql/language/ast"
	"github.com/graphql-go/graphql/language/parser"
	"github.com/graphql-go/graphql/language/printer"
	"github.com/graphql-go/graphql/language/visitor"
)

type gqlRequest struct {
	Query     string  `json:"query"`
	Variables *string `json:"variables"`
}

func AddWhereCondition(body []byte, key, val string, gqlFuncs []string) ([]byte, error) {
	var req gqlRequest
	if err := json.Unmarshal(body, &req); err != nil {
		return nil, err
	}

	newQuery := changeFields(req.Query, key, val, gqlFuncs)
	req.Query = newQuery
	return json.Marshal(&req)
}

func changeFields(query, key, val string, gqlFuncs []string) string {
	ast, err := parser.Parse(parser.ParseParams{Source: query})
	if err != nil {
		log.Fatal(err)
	}
	v := &visitor.VisitorOptions{
		Enter: func(p visitor.VisitFuncParams) (string, interface{}) {
			if node, ok := p.Node.(*sourceAST.Field); ok {
				if node.SelectionSet == nil {
					return visitor.ActionNoChange, nil
				}

				if indexOf(gqlFuncs, node.Name.Value) == -1 {
					return visitor.ActionNoChange, nil
				}

				whereIndex := -1
				for index, arg := range node.Arguments {
					if arg.Name.Value == "where" {
						whereIndex = index
					}
				}

				if whereIndex == -1 {
					changeWithoutWhere(node, key, val)
				} else {
					changeWithWhere(node.Arguments[whereIndex], key, val)
				}
			}
			return visitor.ActionNoChange, nil
		},
	}
	visitor.Visit(ast, v, nil)
	fmt.Println(printer.Print(ast).(string))
	return printer.Print(ast).(string)
}

func changeWithWhere(arg *sourceAST.Argument, key, val string) {
	whereValue := arg.Value.GetValue().([]*sourceAST.ObjectField)
	injectClause := createInjectCondition(key, val)
	whereValue = append(whereValue, injectClause)
	fmt.Printf("where value: %#v\n", whereValue)
	arg.Value = sourceAST.NewObjectValue(&sourceAST.ObjectValue{
		Fields: whereValue,
	})
}

func changeWithoutWhere(node *sourceAST.Field, key, val string) {
	injectClause := createInjectCondition(key, val)

	arg := sourceAST.NewArgument(&sourceAST.Argument{
		Name: sourceAST.NewName(&sourceAST.Name{Value: "where"}),
		Value: sourceAST.NewObjectValue(&sourceAST.ObjectValue{
			Fields: []*sourceAST.ObjectField{injectClause},
		}),
	})

	if node.Arguments == nil {
		node.Arguments = make([]*sourceAST.Argument, 0)
	}

	node.Arguments = append(node.Arguments, arg)
}

func createInjectCondition(key, val string) *sourceAST.ObjectField {
	return sourceAST.NewObjectField(&sourceAST.ObjectField{
		Name: sourceAST.NewName(&sourceAST.Name{Value: key}),
		Value: sourceAST.NewObjectValue(&sourceAST.ObjectValue{
			Fields: []*sourceAST.ObjectField{
				sourceAST.NewObjectField(&sourceAST.ObjectField{
					Name:  sourceAST.NewName(&sourceAST.Name{Value: "_eq"}),
					Value: sourceAST.NewStringValue(&sourceAST.StringValue{Value: val}),
				}),
			},
		}),
	})

}

func indexOf(arr []string, str string) int {
	if arr == nil {
		return -1
	}

	for i, s := range arr {
		if s == str {
			return i
		}
	}

	return -1
}
